package client;

import java.awt.Point;
import java.util.Observable;
import java.util.Random;
import java.util.concurrent.Semaphore;

import nanoxml.XMLElement;
import utils.ITransmittable;
import utils.Piece;
import utils.Player;
import utils.Utils;

import commands.Commands;

import enums.Direction;
import enums.PlayerColor;
import enums.Revealed;
import enums.Weapon;

/**
 * The model in the MVC design pattern. Represents the data of the game.   
 */
public class Model extends Observable implements ITransmittable{

	private Piece [] RPSPieces;			// All the pieces on the board
	private Point lastMove = null;

	// Declare XML attributes
	private static final String XML_ATT_BOARD_PIECES = "BoardPieces";
	private static final String XML_ATT_GAME_STATE = "GameState";
	private static final String XML_ATT_GAME_STATE_BEFORE_PLAYER_DISCONNECTED = "GameStateBeforePlayerDisconnected";
	
	public static final int GAME_STATE_NOT_PLAYING = 0;				// Before two players press Start
	public static final int GAME_STATE_PLACING_FLAG = 1;			// The player is placing the flag
	public static final int GAME_STATE_PLACING_TRAP = 2;			// The player is placing the trap
	public static final int GAME_STATE_BUILD_ARMY = 3;				// The player builds his army (The shuffle button)
	public static final int GAME_STATE_FINISHED_BUILDING_ARMY = 4;	// The player finished to build his army (Done button) and is waiting for the other to build his
	public static final int GAME_STATE_PLAYING = 5;					// The game is on.
	public static final int GAME_STATE_WAITING_FOR_PLAYER = 6;		// When a player suddenly disconnects and we wait for him to return
	public static final int GAME_STATE_GAME_OVER = 7;				// The game is over and one of the players has won.
	
	private static final int NUM_OF_LINE_OF_PIECES = 2;
	private static final int ARMY_LENGTH = Board.NUM_OF_COLS * NUM_OF_LINE_OF_PIECES; // 16;
	private static final int NUM_OF_PIECES = Board.NUM_OF_ROWS * Board.NUM_OF_COLS;
	
	public int mouseX = 0;
	public int mouseY = 0;
	
	public boolean gotEnemyArmy = false;
	private int gameState = GAME_STATE_NOT_PLAYING;
	private int gameStateBeforePlayerDisconnected = GAME_STATE_NOT_PLAYING;
	public PlayerColor ownerColor;
	public PlayerColor playerColor = PlayerColor.NONE;
	public boolean animationOn = false;
	public MovePieceAnimation animation;
	public Weapon enemyWeaponSelect = Weapon.NONE;
	public Weapon selectedWeapon = Weapon.NONE;
	public Semaphore WarDrawSemaphore;
	public boolean trapHide = false;
	private GameSettings gameSettings = null;
	private Player player = null;
	

	public Model (GameSettings newGameSettings, Player mePlayer) {
		super();
		
		// Set up data arrays
		RPSPieces = new Piece [NUM_OF_PIECES];
		WarDrawSemaphore = new Semaphore(0);
		gameSettings = newGameSettings;
		player = mePlayer;
	}

	
	public void convertPlayerColor(int color) {
		playerColor = PlayerColor.Convert(color);
	}
	
	public boolean isOwner () {
		return (playerColor == ownerColor);
	}
	
	/**
	 * Reset the pieces to the initial state.
	 */
	public void reset () {
		// reset last move to off the screen
		lastMove =  new Point (Board.createOutOfBoardPoint());
		int emptyPiecesBoundry = NUM_OF_PIECES - ARMY_LENGTH;
		
		for (int i = 0; i < ARMY_LENGTH; ++i)
			RPSPieces [i] = new Piece(PlayerColor.flipColor(ownerColor), Direction.FRONT, Weapon.NONE, Revealed.NONE);

		for (int i = ARMY_LENGTH; i < emptyPiecesBoundry; ++i)
			RPSPieces [i] = new Piece(Piece.EMPTY);

		for (int i = emptyPiecesBoundry; i < NUM_OF_PIECES; ++i)
			RPSPieces [i] = new Piece(ownerColor, Direction.BACK, Weapon.NONE, Revealed.NONE);

		refreshObservers();
	}

	public Piece getPiece (int x, int y) throws IndexOutOfBoundsException {
		// check position is in bounds and then return it value
		if (x >= 0 && x < Board.NUM_OF_COLS && y >= 0 && y < Board.NUM_OF_ROWS)
			return RPSPieces[y * Board.NUM_OF_ROWS + x];
		else
			return null;
	}
	
	public Piece getPiece (Point p) throws IndexOutOfBoundsException {
		return getPiece((int)p.getX() ,(int) p.getY());
	}

	public void setPiece (int x, int y, Piece newPiece) throws IndexOutOfBoundsException {
		// check position is in bounds and then return it value
		if (x >= 0 && x < Board.NUM_OF_COLS && y >= 0 && y < Board.NUM_OF_ROWS)
			RPSPieces[y * Board.NUM_OF_ROWS + x] = newPiece;
		else
			throw new IndexOutOfBoundsException ();
	}

	public void setPiece (Piece piece, PlayerColor color, Direction direction, Weapon weapon, Revealed revealed) {
		piece.setPiece(color, direction, weapon, revealed);
	}
	
	public void setState(XMLElement message) {
		// Wipe everything
		reset ();
		
		// Set the pieces on the board according to the message
		String [] pieces = new String [RPSPieces.length];
		pieces = Utils.convertToStringArray(message.getStringAttribute(XML_ATT_BOARD_PIECES));
		for (int index = 0 ; index < RPSPieces.length ; ++index) {
			RPSPieces[index] = new Piece(pieces[index]);
		}
		
		// Set the game state
		gameState = message.getIntAttribute(XML_ATT_GAME_STATE);
		gameStateBeforePlayerDisconnected = message.getIntAttribute(XML_ATT_GAME_STATE_BEFORE_PLAYER_DISCONNECTED);
		refreshObservers();
	}
	
	public XMLElement toXML() {
		XMLElement message = new XMLElement ();
		
		message.setName(Commands.MODEL);
		
		String [] piecesMapped = new String[RPSPieces.length];
		for (int index = 0 ; index < RPSPieces.length ; ++index) {
			piecesMapped[index] = (RPSPieces[index].getMap());
		}
		message.setAttribute(XML_ATT_BOARD_PIECES, Utils.valueOf (piecesMapped));
		message.setAttribute(XML_ATT_GAME_STATE, gameState);
		message.setAttribute(XML_ATT_GAME_STATE_BEFORE_PLAYER_DISCONNECTED, gameStateBeforePlayerDisconnected);
		
		return message;	
	}
	
	public void refreshObservers () {
		setChanged();
		notifyObservers();	// notify any class which observe this class
	}
	
	public void start() {
		reset ();
	}

	/**
	 * Randomly assign weapons between the pieces when building the army before the game begins.
	 */
	public void shuffleArmy() {
		
		if (gameState == Model.GAME_STATE_BUILD_ARMY) {
			int weapon;
			int startArmyPosition = getStartArmyPosition();
			int endArmyPosition = startArmyPosition + ARMY_LENGTH;
			
			for (int i = startArmyPosition; i < endArmyPosition; ++i) {
				if (RPSPieces[i].weapon != Weapon.FLAG && RPSPieces[i].weapon != Weapon.TRAP) {
					Random generator = new Random();
					weapon = generator.nextInt(Weapon.values().length);
					while (weapon == Weapon.FLAG.ordinal() || weapon == Weapon.TRAP.ordinal() || weapon == Weapon.NONE.ordinal()) {
						weapon = generator.nextInt(Weapon.values().length);
					}
					RPSPieces[i].weapon = Weapon.Convert(weapon);
				}
			}
			refreshObservers();
		}
	}

	private int getStartArmyPosition() {
		return (isOwner()) ? NUM_OF_PIECES - ARMY_LENGTH : 0;
	}

	public String[] getArmy() {
		int armyPos = getStartArmyPosition();
		String [] armyPieces = new String[ARMY_LENGTH];
		for (int i = 0; i < ARMY_LENGTH; ++i) {
			armyPieces[i] = (RPSPieces[armyPos].getMap());	
			++armyPos;
		}
		return armyPieces;
	}

	public void setEnemyArmy(String[] army) {
		int index = 0;
		int startEnemyArmyPosition = getStartEnemyArmyPosition();
		int endEnemyArmyPosition = startEnemyArmyPosition + ARMY_LENGTH;
		
		for (int i = startEnemyArmyPosition; i < endEnemyArmyPosition; ++i) {
			RPSPieces[i].weapon = new Piece(army [index]).weapon;
			++index;
		}
	}
	
	public void setOwnArmy(String[] army)
	{
		int positionInNewArmy = 0;
		int startArmyPosition = getStartArmyPosition(); 
		int endArmyPosition = startArmyPosition + ARMY_LENGTH;
		
		for (int currPosition = startArmyPosition; currPosition < endArmyPosition; ++currPosition)
		{
			RPSPieces[currPosition] = new Piece(army[positionInNewArmy]);
			
			++positionInNewArmy;
		}
	}
	
	private int getStartEnemyArmyPosition() {
		return Math.abs((NUM_OF_PIECES - ARMY_LENGTH) - getStartArmyPosition());
	}

	public Point getLastMove () {
		return lastMove;
	}
	
	public boolean checkValidMove(Point from, Point to) {
		int distanceX = (int) Math.abs(from.getX() - to.getX());
		int distanceY = (int) Math.abs(from.getY() - to.getY());
		
		return ((distanceX + distanceY) == 1);
	}
	
	public int makeMove(Point from , Point to) {
		Piece winner;
		if (getPiece(to).getMap().equals(new Piece(Piece.EMPTY).getMap())) {
			winner = getPiece(from);
		}
		else {
			winner = makeWar(getPiece(from), getPiece(to));
			if ((getPiece(to).weapon == Weapon.PAPER) || (getPiece(to).weapon == Weapon.ROCK) || (getPiece(to).weapon == Weapon.SCISSORS)) {
				// Check if in Game Settings the user decided to reveal the enemy's weapon after a battle
				if (Boolean.parseBoolean(gameSettings.getSetting(GameSettings.PROPERTY_KEY_DOES_SHOW_ENEMY_WEAPONS_AFTER_BATTLE)))
				{
					winner.revealed = Revealed.YES;
				}
			}
		}
		if (winner == null) {
			return -1;
		}
		if (getPiece(to).weapon == Weapon.FLAG) {
			winner.weapon = Weapon.FLAG;
			winner.revealed = Revealed.YES;
		}
		setPiece((int)to.getX(), (int) to.getY(), winner);
		setPiece((int)from.getX(), (int) from.getY(), new Piece(Piece.EMPTY));
		return 0;
	}
	
	private Piece makeWar(Piece from, Piece to) {
		int result = from.battle(to);
		
		if (result == Piece.WIN) {
			return from;
		}
		else if (result == Piece.LOSE) {
			return to;
		}
		else
			return null;
	}

	public void setOwnerColor (PlayerColor color) {
		ownerColor = color;
	}
	public void setPlayerColor(PlayerColor color) {
		playerColor = color;
	}

	public void setMousePosition(int newMouseX, int newMouseY)
	{
		mouseX = newMouseX;
		mouseY = newMouseY;
	}
	
	public Point getMousePosition()
	{
		return new Point(mouseX, mouseY);
	}

	public int getGameState()
	{
		return gameState;
	}
	
	public void setGameState(int newGameState)
	{
		gameState = newGameState;
	}
	
	public boolean isPlayerViewer()
	{
		return player.isViewer();
	}

	public void saveGameStateBeforePlayerDisconnected()
	{
		gameStateBeforePlayerDisconnected = getGameState();
	}
	
	public void returnGameStateToBeforePlayerDisconnected()
	{
		setGameState(gameStateBeforePlayerDisconnected);
	}
	
	public GameSettings getGameSettings()
	{
		return gameSettings;
	}
	
	public boolean isInPlay()
	{
		return (getGameState() != Model.GAME_STATE_NOT_PLAYING && getGameState() != Model.GAME_STATE_GAME_OVER);
	}
}
